/*
 * Copyright 2007 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.sockets.client.impl;

import com.google.gwt.sockets.client.*;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.RootPanel;

import java.util.ArrayList;
import java.util.List;

/**
 * <p>The Flash-based implementation of text sockets. It uses Flash Applet as proxy to obtain necessary
 * socket funactionality. </p>
 *
 * <p>As we need to support earlier versions of Flash we cannot use LiveConnect, so execution of Flash
 * functions is done with SetVariable. Another problem is limitation of passed data from Flash Applet
 * to browser (Internet Explorer), so Flash should divide data into parts and pass them to browser
 * separately.</p>
 *
 * <p>You should use methods of {@link com.google.gwt.sockets.client.SocketsFactory SocketsFactory} to 
 * create instances of text sockets. Use this class directly only for specific purposes.</p>
 *
 * @see com.google.gwt.sockets.client.TextSocket
 * @see com.google.gwt.sockets.client.SocketsFactory
 * @author Aleksey Lagoshin
 */
public class FlashTextSocketImpl extends TextSocket {

  private static final String TAG =
          "<object id='{0}' width='0px' height='0px'>" +
          "  <param name='movie' value='flashtext.swf'>" +
          "  <param name='play' value='false'>" +
          "  <param name='loop' value='false'>" +
          "  <param name='FlashVars' value='crossdomain={1}&id={2}'/>" +
          "  <embed name='{0}'" +
          "         src='flashtext.swf'" +
          "         FlashVars='crossdomain={1}&id={2}'" +
          "         width='0px'" +
          "         height='0px'" +
          "         play='false'" +
          "         loop='false'" +
          "         swLiveConnect='true'" +
          "  </embed>" +
          "</object>";

  private HTML flashTag;

  private String appletName;

  /**
   * URL to crossdomain policy file.
   */
  private String crossdomain = "";

  /**
   * Buffer for passed data from Flash through onPart function.
   */
  private String partedData = "";

  /**
   * Input buffer which contains received strings terminated by zero byte.
   */
  private List inputBuffer;

  /**
   * Default constructor.
   */
  protected FlashTextSocketImpl() {
  }

  /**
   * Standart constructor for socket classes.
   *
   * @param listener a listener for the socket
   */
  public FlashTextSocketImpl(SocketListener listener) {
    super(listener);
  }

  /**
   * <p>Constructor with additional parameter for URL to crossdomain policy file.</p>
   *
   * <p>More information about crossdomain policy file you can read in comments of
   * {@link com.google.gwt.sockets.client.SocketsFactory SocketsFactory} class.</p>
   *
   * @param listener a listener for the socket
   * @param crossdomain URL to crossdomain policy file, if it is null Flash will download a
   * default crossdomain policy file from the server where this GWT application is located; the
   * default policy file allows Flash to connect to the server where this GWT application is located on
   * ports 1024 and later
   */
  public FlashTextSocketImpl(SocketListener listener, String crossdomain) {
    this.crossdomain = crossdomain != null ? crossdomain : SocketsFactory.getDefaultPolicyFile();
    initialize(listener);
  }

  /**
   * Initializes additional JavaScript functions (workaround for data limitation issue) and adds
   * Flash applet to the page.
   *
   * @param id the ID of this socket
   */
  protected void initImplementation(int id) {
    initAdditionalFunctions(id);

    appletName = "flashApp_" + id;
    flashTag = new HTML();
    flashTag.setSize("0", "0");
    flashTag.setHTML(TAG.replaceAll("[{]0[}]", appletName).replaceAll("[{]1[}]", crossdomain).replaceAll("[{]2[}]", String.valueOf(id)));

    RootPanel.get().add(flashTag);
  }

  public void connect(String host, int port) throws SocketException {
    inputBuffer = new ArrayList();
    super.connect(host, port);
  }

  public int available() throws IOException {
    checkSocketAndConnection(true);
    // Here inputBuffer is always not null
    return inputBuffer.size();
  }

  public String read() throws IOException {
    checkSocketAndConnection(true);
    // Here inputBuffer is always not null
    if (inputBuffer.size() == 0)
      throw new EOFException();

    String data = (String) inputBuffer.get(0);
    inputBuffer.remove(0);
    return data;
  }

  public void shutdown() throws SocketException {
    checkSocket();
    super.shutdown();
    RootPanel.get().remove(flashTag);
  }

  /**
   * Loads crossdomain policy file from specified URL.
   *
   * @param url URL to crossdomain policy file
   * @throws SocketException raised if socket is not ready
   */
  public void loadPolicyFile(String url) throws SocketException {
    if (url == null)
      throw new NullPointerException("URL to Flash crossdomain policy file cannot be null");
    checkSocket();
    loadPolicyFileImpl(url);
  }

  /**
   * Executed when Flash passes the last part of data. Just stores recevied strings in the inputBuffer.
   *
   * @param data received string
   */
  private void onData(String data) {
    inputBuffer.add(data);
  }

  /**
   * Creates additional JavaScript functions to workaround the data limitation issue.
   *
   * @param id the ID of this socket
   */
  private native void initAdditionalFunctions(int id) /*-{
    var prefix = this.@com.google.gwt.sockets.client.AbstractSocket::getPrefix()();
    var instance = this;

    // Flash executes this function when passes the last part of received string
    $wnd[prefix + id + "_onData"] = function(data) {
      // Flash always fires onData event with null parameter after XMLSocket object is created,
      // so we can use it to determine when exactly this applet is initialized. Actually I didn't
      // find other way to do that (the way I do that in binary Flash applet and in Java applets
      // doesn't work for TextSocket Flash applet).
      if (instance.@com.google.gwt.sockets.client.AbstractSocket::socketObj === null) {
        var applet = $doc[instance.@com.google.gwt.sockets.client.impl.FlashTextSocketImpl::appletName];
        instance.@com.google.gwt.sockets.client.AbstractSocket::onReady(Lcom/google/gwt/core/client/JavaScriptObject;)(applet);
      }
      else {
        // Getting the whole string
        var toPass = instance.@com.google.gwt.sockets.client.impl.FlashTextSocketImpl::partedData + data;
        instance.@com.google.gwt.sockets.client.impl.FlashTextSocketImpl::partedData = "";
        instance.@com.google.gwt.sockets.client.impl.FlashTextSocketImpl::onData(Ljava/lang/String;)(toPass);
        // Fires standart onData event
        instance.@com.google.gwt.sockets.client.AbstractSocket::onData(I)(toPass.length);
        // Notifying the Flash applet that browser is now ready for next string
        instance.@com.google.gwt.sockets.client.AbstractSocket::socketObj.SetVariable("ready", "");
      }
    }
    // Flash executes this function when passes part of received string
    $wnd[prefix + id + "_onPart"] = function(data) {
      // Adding part of string to partedData
      instance.@com.google.gwt.sockets.client.impl.FlashTextSocketImpl::partedData += data;
      // Notifying the Flash applet that browser is now ready for next part of string
      instance.@com.google.gwt.sockets.client.AbstractSocket::socketObj.SetVariable("ready", "");
    }
  }-*/;

  private native void loadPolicyFileImpl(String url)/*-{
    this.@com.google.gwt.sockets.client.AbstractSocket::socketObj.SetVariable("crossdomain", url);
  }-*/;

  protected native void connectImpl(String host, int port) /*-{
     this.@com.google.gwt.sockets.client.AbstractSocket::socketObj.SetVariable("connect", host + ":" + port);
  }-*/;

  protected native void disconnectImpl() /*-{
     this.@com.google.gwt.sockets.client.AbstractSocket::socketObj.SetVariable("close", "");
  }-*/;

  protected native void writeImpl(String data) /*-{
    this.@com.google.gwt.sockets.client.AbstractSocket::socketObj.SetVariable("send", data);
  }-*/;

  protected String getPrefix() {
    return "flash_";
  }

}
